package aaa;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import aaa.utils.HashUtil;
import lombok.Data;

import jakarta.persistence.FetchType;
import jakarta.persistence.Column;
import jakarta.persistence.OrderColumn;
import jakarta.persistence.CollectionTable;
import jakarta.persistence.ElementCollection;
import java.security.NoSuchAlgorithmException;
import java.util.Date;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityManagerFactory;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Arrays;

import java.time.ZoneId;
import java.time.LocalDateTime;
import aaa.generated_pb.SimpleFileInfo;
import aaa.generated_pb.DescSafeboxRpl;
import aaa.generated_pb.GreeterGrpc;
import aaa.generated_pb.HelloReply;
import aaa.generated_pb.HelloRequest;
import aaa.generated_pb.PswdLogin;
import aaa.generated_pb.Empty;
import aaa.generated_pb.UserInfo;
import aaa.generated_pb.GetUsersRpl;
import aaa.generated_pb.GetLoginHistoryReq;
import aaa.generated_pb.LoginInfo;
import aaa.generated_pb.GetLoginHistoryRpl;
import aaa.generated_pb.GetResourceInfoRpl;
import aaa.generated_pb.CpuInfo;
import aaa.generated_pb.MemoryInfo;
import aaa.generated_pb.DiskInfo;
import aaa.generated_pb.IOCountersInfo;
import aaa.generated_pb.ConnectionStat;
import aaa.generated_pb.GetNetstatsInfoRpl;
import aaa.generated_pb.InterfaceStat;
import aaa.generated_pb.GetNICInfoRpl;
import aaa.generated_pb.BashHistoryEntry;
import aaa.generated_pb.GetBashHistoryRpl;
import aaa.generated_pb.InboundRule;
import aaa.generated_pb.InboundRuleType;
import aaa.generated_pb.ProcessInfo;

import org.springframework.data.jpa.domain.Specification;
import jakarta.persistence.metamodel.SingularAttribute;
import jakarta.persistence.metamodel.StaticMetamodel;
import jakarta.persistence.AttributeOverride;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.OneToOne;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.CascadeType;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonFormat;

import lombok.EqualsAndHashCode;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;

import java.time.format.DateTimeFormatter;
import java.time.Duration;
import java.lang.Math;
// import javax.xml.bind.annotation.XmlElement;
// import javax.xml.bind.annotation.XmlRootElement;

public class Dt {

    static LocalDateTime old2New(Date d) {
        // java 9
        return LocalDateTime.ofInstant(d.toInstant(), ZoneId.systemDefault());
    }

    static Date new2Old(LocalDateTime l) {
        return Date.from(l.atZone(ZoneId.systemDefault()).toInstant());
    }
    final static public DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    public interface ITeXWorkable {
        String toTeXLine();
        String getTeXEmpty();
        String getTeXTemplate();
    }

    @Data
    static public class W<T> {
        public int code;
        public T data;
        public String msg;
        public W(int code, T data, String msg) {
            this.code = code;
            this.data = data;
            this.msg = msg;
        }

        public W(int code, T data) {
            this.code = code;
            this.data = data;
            this.msg = "success";
        }
    }

    // @XmlRootElement(name = "OpenPort")
    @Data
    @Entity
    // @Table(name = "msgs")
    static public class OpenPort{
        @Hidden
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public Long port;
        @Schema(title = "来源",example = "my-example")
        public String source;
        // @Schema(title = "创建时间",
        //         example = "2000-01-01 12:00:00")
        public Date createTime;
        public Date expiryTime;

        // 🦜 : Nope, not shown on swagger-ui
        // @Schema(required=true, example = "2025-01-01 00:00:00")
        // Date getExpiryTime() {
        //     return expiryTime;
        // }    
    }


    /*
      🦜 : Feels like this structure is kinda common, if there's another class
      using this, ma gonna do inheritance
    */
    // @Data
    // @Entity
    // static public class LoginInfo {
    //     @Id
    //     public String user;
    //     public String passwd;
    // }

    @Data
    @Entity
    static public class SafeboxRecord {
        @Id
        public String user;
        public String passwd;
    }

    @Data
    @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
    static public class SafeboxWithMore extends SafeboxRecord {
        public Date lastAccessTime;
        public Long numFiles;
    }

    @Data
    @Entity
    static public class SafeboxFile{
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String user;
        public String fileName ;
        public Long size ;
        public Date lastModifyTime ;
        public Date lastAccessTime ;
        public String permissions ;
    }

    @Data
    @MappedSuperclass
    static class ChangePswdReq {
        @Hidden
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String pswd;
        public Date expiryTime;
    }

    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
    static class ChangePswdRecord extends ChangePswdReq {
        // @NotNull
        public Date createTime;
    }

    @Data                       // <! 🦜 : we don't cache this in sql, so no need for @Entity
    static class UserInfo2 {
        public String uid;
        public String name;
        public String homeDir;
        public String description;
        public String shell;

        public static UserInfo2 convertFromPb(UserInfo i) {
            Dt.UserInfo2 i2 = new Dt.UserInfo2();
            i2.setUid(i.getUid());
            i2.setName(i.getName());
            i2.setHomeDir(i.getHomeDir());
            i2.setDescription(i.getDescription());
            i2.setShell(i.getShell());
            return i2;
        }
    }

    @Entity
    @Data
    static class LoginInfo2 {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;

        public String user;
        public String terminal;
        public String loginIp;
        public Date loginTime;
        public String logoutTime; // 登出时间 【特殊值释义 crash：系统崩溃或意外关机；down:用户图形界面登出】
        /*
          🦜 : Why `loginTime` is stored as Date, but not `logoutTime`?

          🐢 : Because `logoutTime` is not always a valid date, it can be
          special values such as `crash` or `down`.
        */

        public static LoginInfo2 convertFromPb(LoginInfo i) {
            Dt.LoginInfo2 i2 = new Dt.LoginInfo2();
            i2.setUser(i.getUser());
            i2.setTerminal(i.getTerminal());
            i2.setLoginIp(i.getLoginIp());
            i2.setLoginTime(
                            new2Old(LocalDateTime.parse(i.getLoginTime(), Dt.dtf))
                            // -> the old java.util.Date
                            );
            i2.setLogoutTime(i.getLogoutTime()); // this can be "crash" or "down"
            return i2;
        }
    }

    @StaticMetamodel(LoginInfo2.class)
    static abstract class LoginInfo2_ {
        public static volatile SingularAttribute<LoginInfo2, String> user;
        // public static volatile SingularAttribute<LoginInfo2, String> terminal;
        // public static volatile SingularAttribute<LoginInfo2, String> loginIp;
        public static volatile SingularAttribute<LoginInfo2, Date> loginTime;
        // public static volatile SingularAttribute<LoginInfo2, String> logoutTime;

        public static Specification<LoginInfo2> userContains(String user) {
            return (root, query, builder) -> builder.like(root.get(LoginInfo2_.user), "%" + user + "%");
        }
        public static Specification<LoginInfo2> loginTimeAfter(Date loginTime) {
            return (root, query, builder) -> builder.greaterThan(root.get(LoginInfo2_.loginTime), loginTime);
        }
        public static Specification<LoginInfo2> loginTimeBefore(Date loginTime) {
            return (root, query, builder) -> builder.lessThan(root.get(LoginInfo2_.loginTime), loginTime);
        }
    }


    // --------------------------------------------------
    @Data
    static public class Page<T> {
        // List voList;
        List<T> voList;
        Long totalCount;

        public Page(List<T> voList, Long totalCount) {
            this.voList = voList;
            this.totalCount = totalCount;
        }
        public Page() {}        // required for jackson and swagger generic
    }

    @Data
    static public class Error {
        public static Error SUCCESS = new Error(0, "Success");
        public static Error DUPLICATE_ENTRY = new Error(1001, "duplicate entry");
        public static Error PDF_GENERATE_FAILED = new Error(1002,"pdf generate failed");

        private  int code;
        private  String msg;

        public Error(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public Error() {}
    }

    @Data
    static public class DiskInfo2 {
        public String path;
        public String fstype;
        public Long total;
        public Long free;
        public Long used;
        public Double usedPercent;
        public Long inodesTotal;
        public Long inodesUsed;
        public Long inodesFree;
        public Double inodesUsedPercent;
    }

    @Data
    static public class MemoryInfo2 {
        public Long total;
        public Long available;
        public Long used;
        public Double usedPercent;
    }

    @Data
    static public class CpuInfo2 {
        public Integer cpu;
        public String vendorId;
        public String family;
        public String model;
        public String modelName;
        public Integer stepping;
        public Double mhz;
        public Integer cacheSize;
        public String physicalId;
        public String coreId;
        public Integer cores;
    }

    @Data
    static public class IOCountersInfo2 {
        public Long readCount;
        public Long mergedReadCount;
        public Long writeCount;
        public Long mergedWriteCount;
        public Long readBytes;
        public Long writeBytes;
        public Long readTime;
        public Long writeTime;
        public Long iopsInProgress;
        public Long ioTime;
        public Long weightedIO;
        public String name;
        public String serialNumber;
        public String label;
    }

    /*
      message GetResourceInfoRpl {
      double cpuPercent = 1;
      repeated CpuInfo cpuInfos = 2;
      MemoryInfo memoryInfo = 3;
      DiskInfo diskInfo = 4;
      map<string, IOCountersInfo> ioCountersInfo = 5;
      string hostname = 6;
      }
    */

    @Data
    static public class GetResourceInfoRpl2 {
        public Double cpu_percent;
        public List<CpuInfo2> cpu;
        public MemoryInfo2 memory_info;
        public DiskInfo2 disk;
        public Map<String, IOCountersInfo2> iOCountersInfo;
        public String hostname;

    public static Dt.GetResourceInfoRpl2 convertFromPb(GetResourceInfoRpl r){

        Dt.GetResourceInfoRpl2 r2 = new Dt.GetResourceInfoRpl2();
        // r2.cpuPercent = r.getCpuPercent();
        r2.cpu_percent = r.getCpuPercent(); // ⚙️
        r2.hostname = r.getHostname();

        // 2.1 get the cpuInfos
        // r2.cpuInfos = new ArrayList<Dt.CpuInfo>();
        r2.cpu = new ArrayList<Dt.CpuInfo2>(); // ⚙️
        for (CpuInfo i : r.getCpuInfosList()) {
            Dt.CpuInfo2 i2 = new Dt.CpuInfo2();
            i2.cpu = i.getCpu();
            i2.vendorId = i.getVendorId();
            i2.family = i.getFamily();
            i2.model = i.getModel();
            i2.modelName = i.getModelName();
            i2.stepping = i.getStepping();
            i2.mhz = i.getMhz();
            i2.cacheSize = i.getCacheSize();
            i2.physicalId = i.getPhysicalId();
            i2.coreId = i.getCoreId();
            i2.cores = i.getCores();
            r2.cpu.add(i2);
        }
        // 2.2 get the memoryInfo
        // r2.memoryInfo = new Dt.MemoryInfo();
        r2.memory_info = new Dt.MemoryInfo2(); // ⚙️
        r2.memory_info.total = r.getMemoryInfo().getTotal();
        r2.memory_info.available = r.getMemoryInfo().getAvailable();
        r2.memory_info.used = r.getMemoryInfo().getUsed();
        r2.memory_info.usedPercent = r.getMemoryInfo().getUsedPercent();

        // 2.3 get the diskInfo
        r2.disk = new Dt.DiskInfo2();
        r2.disk.path = r.getDiskInfo().getPath();
        r2.disk.fstype = r.getDiskInfo().getFstype();
        r2.disk.total = r.getDiskInfo().getTotal();
        r2.disk.free = r.getDiskInfo().getFree();
        r2.disk.used = r.getDiskInfo().getUsed();
        r2.disk.usedPercent = r.getDiskInfo().getUsedPercent();
        r2.disk.inodesTotal = r.getDiskInfo().getInodesTotal();
        r2.disk.inodesUsed = r.getDiskInfo().getInodesUsed();
        r2.disk.inodesFree = r.getDiskInfo().getInodesFree();
        r2.disk.inodesUsedPercent = r.getDiskInfo().getInodesUsedPercent();

        // 2.4 get the ioCountersInfo
        r2.iOCountersInfo = new HashMap<String, Dt.IOCountersInfo2>();
        for (Map.Entry<String, IOCountersInfo> i : r.getIoCountersInfoMap().entrySet()) {
            Dt.IOCountersInfo2 i2 = new Dt.IOCountersInfo2();
            i2.readCount = i.getValue().getReadCount();
            i2.mergedReadCount = i.getValue().getMergedReadCount();
            i2.writeCount = i.getValue().getWriteCount();
            i2.mergedWriteCount = i.getValue().getMergedWriteCount();
            i2.readBytes = i.getValue().getReadBytes();
            i2.writeBytes = i.getValue().getWriteBytes();
            i2.readTime = i.getValue().getReadTime();
            i2.writeTime = i.getValue().getWriteTime();
            i2.iopsInProgress = i.getValue().getIopsInProgress();
            i2.ioTime = i.getValue().getIoTime();
            i2.weightedIO = i.getValue().getWeightedIO();
            i2.name = i.getValue().getName();
            i2.serialNumber = i.getValue().getSerialNumber();
            i2.label = i.getValue().getLabel();
            r2.iOCountersInfo.put(i.getKey(), i2);
        }
        return r2;
    }
    }


    /*
      message AddrPort {
      string addr = 1;
      uint32 port = 2;
      }

      message ConnectionStat {
      uint32 fd = 1;
      uint32 family = 2;
      uint32 type = 3;
      AddrPort localAddr = 4;
      AddrPort remoteAddr = 5;
      string status = 6;
      repeated int32 uids = 7;
      int32 pid = 8;
      }
    */

    @Data
    @Entity
    static public class AddrPort {
        @Hidden
        @Id
        @JsonIgnore
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;

        public String addr;
        public Integer port;

        // @OneToOne(optional = false)
        // @JoinColumn(name = "connection_stat_id")
        // ConnectionStat connectionStat;
    }   // class AddrPort

    @Entity
    @Data
    static public class ConnectionStat implements ITeXWorkable {
        @Hidden
        @Id
        @JsonIgnore
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;

        public Integer fd;
        public Integer family;
        public Integer type;
        @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
        @JoinColumn(name = "local_addr_id")
        public AddrPort localAddr;
        @JoinColumn(name = "remote_addr_id")
        @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
        public AddrPort remoteAddr;
        public String status;
        public List<Integer> uids;
        public Integer pid;
        public Boolean closed;
        public String exeName;

        public String getTypeString(){
            String s = type == 1 ? "tcp" : "udp";
            s += (family == 2 ? "v4" : "v6");
            return s;
        }

        @Override
        public int hashCode() { // 🦜: required for objects that holds object fields
            return Objects.hash(id, fd, family, type, localAddr, remoteAddr, status, uids, pid);
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) return true;
            if (obj == null || getClass() != obj.getClass()) return false;
            ConnectionStat other = (ConnectionStat) obj;
            return Objects.equals(id, other.id);
        }

        // static Logger log = LoggerFactory.getLogger(ConnectionStat.class);
        public static final String TeXTemplate =  "port-listening.tex";
        public static final String TeXEmpty = "-- & -- & -- & -- & -- \\\\";
        public String getTeXEmpty(){return TeXEmpty;}
        public String getTeXTemplate (){return TeXTemplate;}
        public String toTeXLine(){
            /*
              🦜 : For each Dt.ConnectionStat, write the line
              <local-addr> & <remote-addr> & <status> & <process> & <protocol> \\
            */
            String s = "";
            s += localAddr.addr + ":" + localAddr.port + " & ";
            s += remoteAddr.addr + ":" + remoteAddr.port + " & ";
            s += "\\texttt{" + status + "} & ";
            s += exeName + " & ";
            s += getTypeString() + " \\\\";

            // replace all the `_` with `\_`
            return s.replaceAll("_", "\\\\_");
        }
    } // class ConnectionStat

    /*
      message IOCounterStat {
      uint64 bytesSent = 1;
      uint64 bytesRecv = 2;
      uint64 packetsSent = 3;
      uint64 packetsRecv = 4;
      uint64 errin = 5;
      uint64 errout = 6;
      uint64 dropin = 7;
      uint64 dropout = 8;
      // uint64 fifoIn = 9; // 🦜 : Not that useful, so not included
      // uint64 fifoOut = 10;
      }
    */

    @Data
    static public class IOCounterStat {
        // @Hidden
        // @Id
        // @JsonIgnore
        // @GeneratedValue(strategy = GenerationType.IDENTITY)
        // public Long id;

        public Long bytesSent;
        public Long bytesRecv;
        public Long packetsSent;
        public Long packetsRecv;
        public Long errin;
        public Long errout;
        public Long dropin;
        public Long dropout;

        public Long recvSpeedBytePerSec;
        public Long sentSpeedBytePerSec;
    }

    @Data                       // 🦜 : No need to persist this, so POJO
    static public class InterfaceStat {
        // @Id
        public Integer index;
        public Integer mtu;
        public String name;
        public String hardwareAddr;
        public List<String> flags;
        public List<String> addrs;
        // @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
        // @JoinColumn(name = "io_counter_stat_id")
        public IOCounterStat ioCounterStat;
        public String defaultGateway;
    }


    // input
    @MappedSuperclass
    @Data
    static public class OldImportantAppAuthRegister { // -> ImportantAppAuthRegister
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String name;
        public String bootPath;
        public String bootHash;
        public String bootSign;
        public String rebootCmd;
        public String owner;

    }

    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false) // 🦜 : we persist this
    static public class OldImportantAppAuthRegisterWithMore extends OldImportantAppAuthRegister {
        public Date rebootTime;
        public Boolean isClean;
        public String reason;

        public String getAppName() {
            return name;
        }

        public static List<OldImportantAppAuthRegisterWithMore> mockList(int n) {

            String[] apps = Dt.apps;
            String[] users = Dt.users;
            String[] reasones = {"正常", "异常", "未知", "未知", "未知"};
            List<Dt.OldImportantAppAuthRegisterWithMore> l = new ArrayList<Dt.OldImportantAppAuthRegisterWithMore>();
            for (int i = 0; i < n; i++) {
                Dt.OldImportantAppAuthRegisterWithMore o = new Dt.OldImportantAppAuthRegisterWithMore();

                o.setName(apps[i % apps.length]); o.setBootPath("/bin/" + o.getName());
                try {
                    o.setBootHash(HashUtil.generateRandomSha256Hash());
                }catch (NoSuchAlgorithmException e){
                    o.setBootHash("xxxxaaaa11112222xxxxbbbb");
                }
                o.setBootSign("sign" + i); o.setRebootCmd("reboot" + i); o.setOwner(users[i % users.length]);
                o.setRebootTime(new Date()); o.setIsClean(i % 2 == 0);
                o.setReason(reasones[i % reasones.length]);
                l.add(o);
                // oldImportantAppAuthRegisterRepo.save(o);
            }
            return l;
        }
    }

    @StaticMetamodel(OldImportantAppAuthRegister.class)
    static abstract class OldImportantAppAuthRegister_ { // 🦜 : Needs to care about: name,user,isClean
        public static volatile SingularAttribute<OldImportantAppAuthRegister, String> name;
        public static volatile SingularAttribute<OldImportantAppAuthRegister, String> owner;

        // public static Specification<OldImportantAppAuthRegister> nameContains(String name) {
        //     return (root, query, builder) -> builder.like(root.get(OldImportantAppAuthRegister_.name), "%" + name + "%");
        // }

        // public static Specification<OldImportantAppAuthRegister> ownerContains(String owner) {
        //     return (root, query, builder) -> builder.like(root.get(OldImportantAppAuthRegister_.owner), "%" + owner + "%");
        // }
    }

    @StaticMetamodel(OldImportantAppAuthRegisterWithMore.class)
    static abstract class OldImportantAppAuthRegisterWithMore_ extends OldImportantAppAuthRegister_ { // 🦜 : Needs to care about: name,user,isClean
        public static volatile SingularAttribute<OldImportantAppAuthRegisterWithMore, Boolean> isClean;



        public static Specification<OldImportantAppAuthRegisterWithMore> nameContains(String name) {
            return (root, query, builder) -> builder.like(root.get(OldImportantAppAuthRegisterWithMore_.name), "%" + name + "%");
        }
        public static Specification<OldImportantAppAuthRegisterWithMore> ownerContains(String owner) {
            return (root, query, builder) -> builder.like(root.get(OldImportantAppAuthRegisterWithMore_.owner), "%" + owner + "%");
        }

        public static Specification<OldImportantAppAuthRegisterWithMore> isCleanEquals(Boolean isClean) {
            return (root, query, builder) -> builder.equal(root.get(OldImportantAppAuthRegisterWithMore_.isClean), isClean);
        }
    }


    static final String[] cmds = {"ls -l", "ps -ef", "cat /etc/passwd",
                                  "cat /etc/shadow", "cat /etc/group", "cat /etc/gshadow",
                                  "cat /etc/hosts", "cat /etc/hostname", "cat /etc/resolv.conf",
                                  "cat /etc/ssh/sshd_config", "cat /etc/ssh/ssh_config",
                                  "ps -ef | grep ssh", "ps -ef | grep telnet", "ps -ef | grep ftp",
    };

    @Entity
    @Data
    static public class OldAppAuthFailed { // -> AppAuthFailed ⚙️
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public Long appId;
        public String appName;
        public String actBootPath;
        public String actBootHash;
        public Date verifyTimestamp;
        public String verifyPreSoft;
        public Integer reason; //0:未经注册应用 1:应用被非法替换 2:应用被未授权用户启动
        public String pid;
        public String userName;
        public String cmd;
        public boolean isClose;

        public static List<OldAppAuthFailed> mockList(int n) {

            String[] cmds = Dt.cmds;
            String[] users = Dt.users;
            List<Dt.OldAppAuthFailed> l = new ArrayList<Dt.OldAppAuthFailed>();
            for (int i = 0; i < n; i++) {
                Dt.OldAppAuthFailed o = new Dt.OldAppAuthFailed();

                o.setAppId((long)i); o.setAppName("app" + i); o.setActBootPath("/bin/app" + i);
                try {
                    o.setActBootHash(HashUtil.generateRandomSha256Hash());
                }catch (NoSuchAlgorithmException e){
                    o.setActBootHash("xxxxaaaa11112222xxxxbbbb");
                }

                o.setVerifyTimestamp(new Date()); o.setVerifyPreSoft("preSoft" + i); o.setReason(i % 3);
                o.setPid("pid" + i);
                o.setUserName(users[i % users.length]);
                o.setCmd(cmds[i % cmds.length]);
                o.setClose(i % 2 == 0);
                l.add(o);
                // oldAppAuthFailedRepo.save(o);
            }
            return l;
        }
    }

    @StaticMetamodel(OldAppAuthFailed.class)
    static abstract class OldAppAuthFailed_ {
        public static volatile SingularAttribute<OldAppAuthFailed, Integer> reason;
        public static volatile SingularAttribute<OldAppAuthFailed, String> userName;

        public static Specification<OldAppAuthFailed> reasonEquals(Integer reason) {
            return (root, query, builder) -> builder.equal(root.get(OldAppAuthFailed_.reason), reason);
        }

        public static Specification<OldAppAuthFailed> userNameContains(String userName) {
            return (root, query, builder) -> builder.like(root.get(OldAppAuthFailed_.userName), "%" + userName + "%");
        }
    }

    @Data
    static public class DeviceInterface {
        public String type;     // "usb3, usb2, typec"

        public String code;
        public Date lastConnectionTime;
        public Boolean disabled;

        public DeviceInterface(String type, String code, Date lastConnectionTime, Boolean disabled) {
            this.type = type;
            this.code = code;
            this.lastConnectionTime = lastConnectionTime;
            this.disabled = disabled;
        }
    }

    @Data
    @MappedSuperclass
    static public class ApplicantIn {
        public String name = "name";
        public String phone;
        public String sfz;
        public String code;
        public String department;
        public Date expiryTime;
    }

    @Data
    @Entity
    @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
    static public class Applicant extends ApplicantIn {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public Date createTime;

        public Boolean illegal = true;
        public Boolean getExpired() {
            return new Date().after(expiryTime);
        }
        // public void setExpired(Boolean expired) {}

        public static List<Applicant> mockList(int n) {

            String[] users = Dt.users;
            String[] phones = {"188-1239-2345", "188-5784-2379", "188-2346-5667"};
            String[] names = {"李翔宇", "张丽安"};
            String[] sfzs = {"123456789012345678", "123456789012345679", "123456789012345670"};

            List<Dt.Applicant> l = new ArrayList<Dt.Applicant>();
            for (int i = 0; i < n; i++) {
                Dt.Applicant a = new Dt.Applicant();

                a.setPhone(phones[i % phones.length]);
                a.setName(names[i % names.length]);
                a.setSfz(sfzs[i % sfzs.length]);
                try {
                    a.setCode(HashUtil.generateRandomSha256Hash());
                }catch (NoSuchAlgorithmException e){
                    a.setCode("xxxxaaaa11112222xxxxbbbb");
                }
                a.setDepartment("部门" + i);

                if (i < 5){
                    a.setExpiryTime(new Date());
                }else{              // not expired
                    Calendar c = Calendar.getInstance();
                    c.set(2040, 1, 1, 1,1,1);
                    a.setExpiryTime(c.getTime());
                }

                a.setCreateTime(new Date());
                l.add(a);
            }

            return l;
        }
    }

    @StaticMetamodel(ApplicantIn.class)
    static abstract class ApplicantIn_ {
        public static volatile SingularAttribute<Applicant, String> code;
        public static volatile SingularAttribute<Applicant, String> sfz;
        public static volatile SingularAttribute<Applicant, String> department;

        public static volatile SingularAttribute<Applicant, String> name;
        public static volatile SingularAttribute<Applicant, String> phone;
        public static volatile SingularAttribute<Applicant, Date> expiryTime;

        public static Specification<Applicant> nameContains(String name) {
            return (root, query, builder) -> builder.like(root.get(Applicant_.name), "%" + name + "%");
        }

        public static Specification<Applicant> phoneContains(String phone) {
            return (root, query, builder) -> builder.like(root.get(Applicant_.phone), "%" + phone + "%");
        }

        public static Specification<Applicant> expiryTimeAfter(Date expiryTime) {
            return (root, query, builder) -> builder.greaterThan(root.get(Applicant_.expiryTime), expiryTime);
        }

        public static Specification<Applicant> expiryTimeBefore(Date expiryTime) {
            return (root, query, builder) -> builder.lessThan(root.get(Applicant_.expiryTime), expiryTime);
        }
    }

    @StaticMetamodel(Applicant.class)
    static abstract class Applicant_ extends ApplicantIn_
    {
        public static volatile SingularAttribute<Applicant, Date> createTime;
        public static volatile SingularAttribute<Applicant, Long> id;
        public static volatile SingularAttribute<Applicant, Boolean> illegal;

        public static Specification<Applicant> createTimeAfter(Date createTime) {
            return (root, query, builder) -> builder.greaterThan(root.get(Applicant_.createTime), createTime);
        }
        public static Specification<Applicant> createTimeBefore(Date createTime) {
            return (root, query, builder) -> builder.lessThan(root.get(Applicant_.createTime), createTime);
        }
    }

    @Data
    @MappedSuperclass
    static public class DeviceRecordIn {
        String name;
        String code;
        String type;
        String auth;
        Date expiryTime;
    }

    @Entity
    @Data
    @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
    static public class DeviceRecord extends DeviceRecordIn {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public Date createTime;
    }

    @StaticMetamodel(DeviceRecordIn.class)
    static abstract class DeviceRecordIn_ {

        public static volatile SingularAttribute<DeviceRecord, String> code;
        public static volatile SingularAttribute<DeviceRecord, Date> expiryTime;

        public static volatile SingularAttribute<DeviceRecord, String> name;
        public static volatile SingularAttribute<DeviceRecord, String> type;
        public static volatile SingularAttribute<DeviceRecord, String> auth;

        public static Specification<DeviceRecord> nameContains(String name) {
            return (root, query, builder) -> builder.like(root.get(DeviceRecord_.name), "%" + name + "%");
        }

        public static Specification<DeviceRecord> typeEquals(String type) {
            return (root, query, builder) -> builder.equal(root.get(DeviceRecord_.type), type);
        }
        public static Specification<DeviceRecord> authEquals(String auth) {
            return (root, query, builder) -> builder.equal(root.get(DeviceRecord_.auth), auth);
        }

    }

    @StaticMetamodel(DeviceRecord.class)
    static abstract class DeviceRecord_ extends DeviceRecordIn_
    {
        public static volatile SingularAttribute<DeviceRecord,Long> id;
        public static volatile SingularAttribute<DeviceRecord, Date> createTime;
        public static Specification<DeviceRecord> createTimeAfter(Date createTime) {
            return (root, query, builder) -> builder.greaterThan(root.get(DeviceRecord_.createTime), createTime);
        }
        public static Specification<DeviceRecord> createTimeBefore(Date createTime) {
            return (root, query, builder) -> builder.lessThan(root.get(DeviceRecord_.createTime), createTime);
        }

    }

    @Entity
    @Data
    static public class IllegalDeviceRecord {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String name;

        public String code;
        public String type;     // 非法类型
        public Date foundTime;
        public String hash;
    }

    @StaticMetamodel(IllegalDeviceRecord.class)
    static abstract class IllegalDeviceRecord_ {
        public static volatile SingularAttribute<IllegalDeviceRecord, Long> id;
        public static volatile SingularAttribute<IllegalDeviceRecord, String> hash;


        public static volatile SingularAttribute<IllegalDeviceRecord, String> name;
        public static volatile SingularAttribute<IllegalDeviceRecord, String> code;
        public static volatile SingularAttribute<IllegalDeviceRecord, Date> foundTime;
        public static volatile SingularAttribute<IllegalDeviceRecord, String> type;

        public static Specification<IllegalDeviceRecord> nameContains(String name) {
            return (root, query, builder) -> builder.like(root.get(IllegalDeviceRecord_.name), "%" + name + "%");
        }

        public static Specification<IllegalDeviceRecord> codeContains(String code) {
            return (root, query, builder) -> builder.like(root.get(IllegalDeviceRecord_.code), "%" + code + "%");
        }

        public static Specification<IllegalDeviceRecord> typeEquals(String type) {
            return (root, query, builder) -> builder.equal(root.get(IllegalDeviceRecord_.type), type);
        }

        public static Specification<IllegalDeviceRecord> foundTimeAfter(Date foundTime) {
            return (root, query, builder) -> builder.greaterThan(root.get(IllegalDeviceRecord_.foundTime), foundTime);
        }
        public static Specification<IllegalDeviceRecord> foundTimeBefore(Date foundTime) {
            return (root, query, builder) -> builder.lessThan(root.get(IllegalDeviceRecord_.foundTime), foundTime);
        }
    }

    @Entity
    @Data
    public static class GenericCounter {
        /*
          🦜 : This counter is initially used to count the check time.
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        Long id;
        String name;
        Date createTime;
        public static GenericCounter create(String n){
            GenericCounter o = new GenericCounter();
            o.setName(n);
            o.setCreateTime(new Date());
            return o;
        }
    }

    /* 🦜 : For this one, I think jpa method is enough: findByNameAndCreateTimeBetween

      @StaticMetamodel(GenericCounter.class)
      public static abstract class GenericCounter_ {
      public static volatile SingularAttribute<GenericCounter, String> name;
      public static volatile SingularAttribute<GenericCounter, Date> createTime;

      public static Specification<GenericCounter> nameEquals(String name) {
      return (root, query, builder) -> builder.equal(root.get(GenericCounter_.name), name);
      }

      public static Specification<GenericCounter> createTimeBetween(Date from, Date to) {
      return (root, query, builder) -> builder.between(root.get(GenericCounter_.createTime), from, to);
      }
      }
     */

    @Data
    static public class DailyRecord {
        public Date date;
        // public String dateDay;

        public Long x1;

        public Long y1; public Long y2;

        public Long z1; public Long z2; public Long z3;

        public static DailyRecord mock(Date date, Long x1) {
            DailyRecord o = new DailyRecord();
            o.date = date;
            o.x1 = x1;

            // 🦜 : randomize the rest
            Random rand = new Random();
            o.y1 = rand.nextLong(100) + 1; o.y2 = rand.nextLong(100) + 1;
            o.z1 = rand.nextLong(100) + 1; o.z2 = rand.nextLong(100) + 1; o.z3 = rand.nextLong(100) + 1;
            return o;
        }

        public String getDateDay(){
            // return YYYY-MM-DD
            LocalDateTime d = old2New(date);
            return d.format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
        }
    }


    @Data
    static public class ManyCounters {
        /*
          Long: x2, y1,y2,y5
         */
        static ManyCounters o = ManyCounters.mock();
        public static ManyCounters next() {

            Random rand = new Random();
            // 🦜 add a random number to each field
            o.x1 += rand.nextLong(10);o.x2 += rand.nextLong(10);o.x3 += rand.nextLong(10);
            o.y1 += rand.nextLong(10);o.y2 += rand.nextLong(10);o.y3 += rand.nextLong(10);o.y4 += rand.nextLong(10);o.y5 += rand.nextLong(10);
            o.z1 += rand.nextLong(10);o.z2 += rand.nextLong(10);
            o.a1 += rand.nextLong(10);o.a2 += rand.nextLong(10);o.a3 += rand.nextLong(10);
            o.b1 += rand.nextLong(10);

            // update the first x1
            o.dailyRecords.get(0).x1 = o.x1;
            return o;
        }

        public Long x1; public Long x2; public Long x3;

        public Long y1; public Long y2; public Long y3; public Long y4; public Long y5;

        public Long z1; public Long z2;

        public Long a1; public Long a2; public Long a3;

        public Long b1;

        public List<DailyRecord> dailyRecords = new ArrayList<>();

        public static ManyCounters mock() { // 🦜 : the mocked one
            ManyCounters o = new ManyCounters();
            Random rand = new Random();

            o.x1 = rand.nextLong(500) + 1; o.x2 = rand.nextLong(100) + 1; o.x3 = rand.nextLong(100) + 1;

            o.y1 = rand.nextLong(100) + 1; o.y2 = rand.nextLong(100) + 1; o.y3 = rand.nextLong(100) + 1;
            o.y4 = rand.nextLong(100) + 1; o.y5 = rand.nextLong(100) + 1;

            o.z1 = rand.nextLong(100) + 1; o.z2 = rand.nextLong(100) + 1;

            o.a1 = rand.nextLong(100) + 1; o.a2 = rand.nextLong(100) + 1; o.a3 = rand.nextLong(100) + 1;

            o.b1 = rand.nextLong(100) + 1;

            // 🦜 push 7 days from now
            // o.dailyRecords = new ArrayList<>();
            Calendar cal = Calendar.getInstance();
            for (int i = 0; i < 7; i++) {
                // 🦜 : make it fluctuate between -50,50
                Long xx1 = o.x1 + i * (rand.nextLong(100) - 50);
                // zero if negative
                o.dailyRecords.add(DailyRecord.mock(cal.getTime(), xx1 < 0 ? 0 : xx1));
                cal.add(Calendar.DATE, -1);
            }
            return o;
        }
    } // class ManyCounters

    @Data
    static public class OldRunDetail {
        public Date boot_time;

        public Integer getHourSinceBoot(){
            LocalDateTime d = old2New(boot_time);
            LocalDateTime now = LocalDateTime.now();
            Duration duration = Duration.between(d, now);
            return (int)duration.toHours();
        }
    }

    @Data
    static public class OldTrustablePointsVo {

        public static OldTrustablePointsVo mock(){
            OldTrustablePointsVo o = new OldTrustablePointsVo();
            o.envPoints = 80.2;
            o.actionPoints = 99.3;
            o.userPoints = 100;
            o.contentPoints = 100;
            // average
            o.totalPoints = (o.envPoints + o.actionPoints + o.userPoints + o.contentPoints) / 4;
            return o;
        }

        public double totalPoints ;
        public double envPoints ;
        public double actionPoints ;
        public double userPoints ;
        public double contentPoints ;
    }

    @Data
    static public class OldPresoftStatus {
        public static OldPresoftStatus o = new OldPresoftStatus();
        public static OldPresoftStatus next() {
            Random rand = new Random();
            o.interactCount += rand.nextInt(10);
            return o;
        }
        public OldPresoftStatus() {
            this.code = 0;
            this.pid = "1234";
            try {
                this.preSoftHash = HashUtil.generateRandomSha256Hash();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
                this.preSoftHash = "1234567890123456789012345678901234567890123456789012345678901234";
            }
            this.interactCount = 100L;
            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.DATE, -1);
            this.LastCheck = cal.getTime();
        }
        public Integer code;
        public String pid;
        public String preSoftHash;
        public Long interactCount;
        public Date LastCheck;
    }

    static final String[] users = {"root", "me", "aaa", "postgres", "mysql", "redis", "mongodb", "bbb", "admin"};

    @Data
    static public class OldLoginHistory{
        public String userName;
        public Date loginTime;
        public String logoutTime; // special values: crash, timeout
        public String loginTerminal;
        public String loginFrom;
        public int reason;      // 1: 未授权登录， 2： 超出有效时间
        public int recordHash;

        public static List<OldLoginHistory> mockList(int n) {
            Calendar cal = Calendar.getInstance();
            String[] addrs = {"10.0.0.2", "fd00::1", "10.0.0.3", "fd00::2",
                              "192.168.1.202","192.168.1.164"};

            String[] users = Dt.users;
            List<Dt.OldLoginHistory> l3 = new ArrayList<Dt.OldLoginHistory>();
            for (int i = 0; i < n; i++) {
                Dt.OldLoginHistory o = new Dt.OldLoginHistory();
                o.setUserName(users[i % users.length]);
                cal.set(2023, 1, i + 1, 0, 0, 1);
                o.setLoginTime(cal.getTime());
                cal.set(2023, 1, i + 1, 0, 0, 2);
                o.setLogoutTime("2023-02-" + (i + 1) + " 00:00:02");
                o.setLoginTerminal("tty" + (i % 3));
                o.setLoginFrom(addrs[i % addrs.length]);
                o.setReason(i % 2 == 0 ? 1 : 2);
                o.setRecordHash(o.hashCode());

                // oldLoginHistory.save(o);
                l3.add(o);
            }
            return l3;
        }


    public static Dt.OldLoginHistory oldFromNew(LoginInfo2 i){
        Dt.OldLoginHistory o = new Dt.OldLoginHistory();
        o.setUserName(i.getUser());
        o.setLoginTime(i.getLoginTime());
        o.setLogoutTime(i.getLogoutTime());
        o.setLoginTerminal(i.getTerminal());
        o.setLoginFrom(i.getLoginIp());

        o.setReason(0);
        o.setRecordHash(o.hashCode());

        return o;
    }

    }


    static final String[] apps = {"mysql", "redis", "mongodb", "postgres", "nginx", "apache", "tomcat", "java", "python", "nodejs",
                                  "docker", "k8s", "etcd", "consul", "vault", "haproxy", "keepalived", "nginx", "apache", "tomcat", "java", "python", "nodejs",
                                  "cfw", "mariadb"};
    @Entity
    @Data
    static public class OldAppGreenListDao {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String appName;
        public String owner;
        public String bootPath;
        public String bootHash;
        public Date rebootTime;
        public boolean isClean = true;
        public String reason;

        public static List<OldAppGreenListDao> mockList(int n) {
            String[] users = Dt.users;
            List<Dt.OldAppGreenListDao> l2 = new ArrayList<Dt.OldAppGreenListDao>();
            String[] apps = Dt.apps;
            String[] reasons = {"系统监管用的", "测试用的", "开发用的", "运维用的"};
            Calendar cal = Calendar.getInstance();

            for (int i = 0; i < 4; i++) {
                Dt.OldAppGreenListDao o = new Dt.OldAppGreenListDao();
                o.setAppName(apps[i % apps.length]); o.setOwner(users[i % users.length]);
                o.setBootPath("/bin/" + o.getAppName());

                try {
                    o.setBootHash(HashUtil.generateRandomSha256Hash());
                }catch (NoSuchAlgorithmException e){
                    o.setBootHash("xxxxaaaa11112222xxxxbbbb");
                }

                cal.set(2023, 1, i + 1, 0, 0, 1);
                o.setRebootTime(cal.getTime());
                o.setClean(i % 2 == 0);
                o.setReason(reasons[i % reasons.length]);

                l2.add(o);
            }
            return l2;
        }
    }

    @StaticMetamodel(OldAppGreenListDao.class)
    static abstract class OldAppGreenListDao_ {
        public static volatile SingularAttribute<OldAppGreenListDao, String> appName;
        public static volatile SingularAttribute<OldAppGreenListDao, String> owner;
        public static Specification<OldAppGreenListDao> appNameContains(String appName) {
            return (root, query, builder) -> builder.like(root.get(OldAppGreenListDao_.appName), "%" + appName + "%");
        }

        public static Specification<OldAppGreenListDao> ownerContains(String owner) {
            return (root, query, builder) -> builder.like(root.get(OldAppGreenListDao_.owner), "%" + owner + "%");
        }
    }

    @Entity
    @Data
    static public class OldAppAuthRegister {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String name;
        public String bootPath;
        public String bootHash;
        public String bootSign;
        public String owner;
        public String descrb;
        public Date createTime;

        public static List<OldAppAuthRegister> mockList(int n) {

            String[] apps = Dt.apps;
            String[] users = Dt.users;
            String[] descs = {"系统监管用的", "测试用的", "开发用的", "运维用的"};
            List<Dt.OldAppAuthRegister> l = new ArrayList<Dt.OldAppAuthRegister>();

            Calendar cal = Calendar.getInstance();
            for (int i = 0; i < n; i++) {
                Dt.OldAppAuthRegister o = new Dt.OldAppAuthRegister();

                o.setName(apps[i % apps.length]);
                o.setBootPath("/bin/app" + i);
                try {
                    o.setBootHash(HashUtil.generateRandomSha256Hash());
                }catch (NoSuchAlgorithmException e){
                    o.setBootHash("xxxxaaaa11112222xxxxbbbb");
                }
                o.setBootSign("sign" + i); o.setOwner(users[i % users.length]);
                o.setDescrb(descs[i % descs.length]);
                l.add(o);
                // oldAppAuthRegisterRepo.save(o);

                cal.set(2023, 1, i + 1, 0, 0, 1);
                o.setCreateTime(cal.getTime());
            }
            return l;
        }

    }

    @StaticMetamodel(OldAppAuthRegister.class)
    static abstract class OldAppAuthRegister_ {
        public static volatile SingularAttribute<OldAppAuthRegister, String> name;
        public static volatile SingularAttribute<OldAppAuthRegister, String> owner;

        public static Specification<OldAppAuthRegister> nameContains(String name) {
            return (root, query, builder) -> builder.like(root.get(OldAppAuthRegister_.name), "%" + name + "%");
        }

        public static Specification<OldAppAuthRegister> ownerContains(String owner) {
            return (root, query, builder) -> builder.like(root.get(OldAppAuthRegister_.owner), "%" + owner + "%");
        }
    }

    @Data
    static public class OldMachineUserInfo {
        public String id;
        public String name;
        public String workDir;
        public String binShell;
        public Date lastLogin;
        public Boolean legal;

        public static List<OldMachineUserInfo> mockList(int n) {
            // String[] users = {"root", "me", "aaa", "postgres", "mysql", "redis", "mongodb"};
            String[] users = Dt.users;
            String[] shs = {"bash", "sh", "zsh", "csh", "tcsh", "ksh", "fish", "dash", "ash", "zsh"};
            List<OldMachineUserInfo> l2 = new ArrayList<OldMachineUserInfo>();
            for (int i = 0; i < 4; i++) {
                OldMachineUserInfo o = new OldMachineUserInfo();
                o.setName(users[i % users.length]); o.setWorkDir("/home/" + o.getName()); o.setBinShell("/bin/" + shs[i % shs.length]);
                o.setLastLogin(new Date()); o.setLegal(true);

                // oldMachineUserRepo.save(o);
                l2.add(o);
            }
            return l2;
        }

        public static OldMachineUserInfo oldFromNew(UserInfo2 i){
            /*

              public String uid;
              public String name;
              public String homeDir;
              public String description;
              public String shell;
             */
            OldMachineUserInfo o = new OldMachineUserInfo();
            o.setId(i.getUid());
            o.setName(i.getName());
            o.setWorkDir(i.getHomeDir());
            o.setBinShell(i.getShell());
            o.setLegal(true);

            Calendar cal = Calendar.getInstance();
            cal.add(Calendar.DATE, -1);
            o.setLastLogin(cal.getTime());

            return o;
        }
    }

    @Entity
    @Data
    static public class OldImportantAppAuthFailed {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public Long appId;
        public String appName;
        public String actName;
        public String userName;
        public String rebootCmd;
        public Date verifyTimestamp;
        public String verifyPreSoft;
        public Boolean isOpen;
        // public String txHash; // 🦜 : No chain anymore, so no need to persist this

        public static List<OldImportantAppAuthFailed> mockList(int n) {

            String[] apps = Dt.apps;
            String[] users = Dt.users;
            List<Dt.OldImportantAppAuthFailed> l = new ArrayList<Dt.OldImportantAppAuthFailed>();
            for (int i = 0; i < n; i++) {
                Dt.OldImportantAppAuthFailed o = new Dt.OldImportantAppAuthFailed();

                o.setAppId((long)i); o.setAppName(apps[i % apps.length]); o.setActName("/bin/" + o.getAppName());
                o.setUserName(users[i % users.length]); o.setRebootCmd("reboot" + i);
                o.setVerifyTimestamp(new Date()); o.setVerifyPreSoft("preSoft" + i); o.setIsOpen(i % 2 == 0);
                // o.setTxHash("txHash" + i);
                l.add(o);
            }
            return l;
        }
    }

    @StaticMetamodel(OldImportantAppAuthFailed.class)
    static abstract class OldImportantAppAuthFailed_ {
        public static volatile SingularAttribute<OldImportantAppAuthFailed, String> appName;
        public static volatile SingularAttribute<OldImportantAppAuthFailed, String> userName;

        public static Specification<OldImportantAppAuthFailed> appNameContains(String appName) {
            return (root, query, builder) -> builder.like(root.get(OldImportantAppAuthFailed_.appName), "%" + appName + "%");
        }

        public static Specification<OldImportantAppAuthFailed> userNameContains(String userName) {
            return (root, query, builder) -> builder.like(root.get(OldImportantAppAuthFailed_.userName), "%" + userName + "%");
        }
    }

    @Entity
    @Data
    public static class BashHistoryEntry2{
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String cmd;
        public String time;              // 🦜 : can be special value: unknown
        public String user;

        public static BashHistoryEntry2 convertFromPb(BashHistoryEntry e){
            BashHistoryEntry2 o = new BashHistoryEntry2();
            o.setCmd(e.getCmd());
            o.setTime(e.getTime());
            o.setUser(e.getUser());
            return o;
        }

    }
    /*
      message BashHistoryEntry {
      string cmd = 1;
      string time = 2;              // 🦜 : can be special value: unknown
      string user = 3;
      }
     */

    @StaticMetamodel(value = BashHistoryEntry2.class)
    static abstract class BashHistoryEntry2_ {

        public static volatile SingularAttribute<BashHistoryEntry2, String> cmd;
        public static volatile SingularAttribute<BashHistoryEntry2, String> time;
        public static volatile SingularAttribute<BashHistoryEntry2, String> user;

        public static Specification<BashHistoryEntry2> cmdContains(String cmd) {
            return (root, query, builder) -> builder.like(root.get(BashHistoryEntry2_.cmd), "%" + cmd + "%");
        }

        public static Specification<BashHistoryEntry2> userContains(String user) {
            return (root, query, builder) -> builder.like(root.get(BashHistoryEntry2_.user), "%" + user + "%");
        }

        public static Specification<BashHistoryEntry2> timeAfter(String time) {
            return (root, query, builder) -> builder.greaterThan(root.get(BashHistoryEntry2_.time), time);
        }

        public static Specification<BashHistoryEntry2> timeBefore(String time) {
            return (root, query, builder) -> builder.lessThan(root.get(BashHistoryEntry2_.time), time);
        }
    }

    @Entity
    @Data
    public static class OldBashHistoryDao {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public String userName;
        public String cmd;
        public String optTime;
        public boolean isIllegal;
        public int recordHash;

        public static List<OldBashHistoryDao> mockList(int n) {
            String[] users = Dt.users;
            String[] cmds = Dt.cmds;
            List<Dt.OldBashHistoryDao> l2 = new ArrayList<Dt.OldBashHistoryDao>();

            for (int i = 0; i < n; i++) {
                Dt.OldBashHistoryDao o = new Dt.OldBashHistoryDao();
                o.setUserName(users[i % users.length]);
                o.setCmd(cmds[i % cmds.length]);
                o.setOptTime("2023-02-" + (i + 1) + " 00:00:02");
                o.setIllegal(i % 2 == 0);
                o.setRecordHash(o.hashCode());

                l2.add(o);
            }
            return l2;
        }

        public static OldBashHistoryDao oldFromNew(BashHistoryEntry2 i){
            OldBashHistoryDao o = new OldBashHistoryDao();

            o.setUserName(i.getUser());
            o.setCmd(i.getCmd());
            o.setOptTime(i.getTime());

            o.setIllegal(false);
            o.setRecordHash(o.hashCode());
            return o;
        }
    }

    @StaticMetamodel(OldBashHistoryDao.class)
    static abstract class OldBashHistoryDao_ {
        public static volatile SingularAttribute<OldBashHistoryDao, String> userName;
        public static volatile SingularAttribute<OldBashHistoryDao, Date> optTime;
        public static volatile SingularAttribute<OldBashHistoryDao, Boolean> isIllegal;

        public static Specification<OldBashHistoryDao> userNameContains(String userName) {
            return (root, query, builder) -> builder.like(root.get(OldBashHistoryDao_.userName), "%" + userName + "%");
        }

        public static Specification<OldBashHistoryDao> optTimeAfter(Date optTime) {
            return (root, query, builder) -> builder.greaterThan(root.get(OldBashHistoryDao_.optTime), optTime);
        }

        public static Specification<OldBashHistoryDao> optTimeBefore(Date optTime) {
            return (root, query, builder) -> builder.lessThan(root.get(OldBashHistoryDao_.optTime), optTime);
        }

        public static Specification<OldBashHistoryDao> isIllegalEquals(Boolean isIllegal) {
            return (root, query, builder) -> builder.equal(root.get(OldBashHistoryDao_.isIllegal), isIllegal);
        }
    }

    /*
      enum InboundRuleType {
      tcp = 0;
      udp = 1;
      icmp = 2;
      all = 3;
      }

      message InboundRule {
      InboundRuleType type = 1;
      string portRange = 2;         // e.g. 22, 80:90, all=1:65535
      string source = 3;            // e.g. 0.0.0.0/0
      bool ipv6 = 4;
      }

      message SetInboundRulesReq {
      repeated InboundRule rules = 1;
      }
     */

    @Data
    public static class InboundRule2 implements ITeXWorkable {
        String type;            // tcp | udp | icmp | all
        String portRange;
        String source;
        Boolean ipv6;

        public static final String TeXTemplate =  "inbound-rules.tex";
        public static final String TeXEmpty =  "-- & -- & -- & --\\\\";
        @JsonIgnore
        public String getTeXEmpty(){
            return TeXEmpty;
        }
        @JsonIgnore
        public String getTeXTemplate (){
            return TeXTemplate;
        }
        public String toTeXLine(){

            /*
              🦜 : For each, write the line
              <type> & <portRange> & <source> & <ipv6> \\
            */
            String s = "";
            s += type + " & ";
            s += portRange + " & ";
            s += source + " & ";
            s += (ipv6 ? "\\emoji{keycap-6}" : "\\emoji{keycap-4}") + " \\\\";

            // replace all the `_` with `\_`
            return s.replaceAll("_", "\\\\_");
        }

        public InboundRule convertToPb(){
            InboundRule.Builder b = InboundRule.newBuilder();
            if (type.equals("tcp")){
                b.setType(InboundRuleType.tcp);
            }else if (type.equals("udp")){
                b.setType(InboundRuleType.udp);
            }else if (type.equals("icmp")){
                b.setType(InboundRuleType.icmp);
            }else {
                b.setType(InboundRuleType.all);
            }

            b.setPortRange(portRange);
            b.setSource(source);
            b.setIpv6(ipv6);

            return b.build();
        }

    } // class InboundRule2

    /*
      message ProcessIOCountersStat {
      uint64 readCount = 1;
      uint64 writeCount = 2;
      uint64 readBytes = 3;
      uint64 writeBytes = 4;
      }

      message ProcessInfo {
      string name = 1;
      string user = 2;
      int32 pid = 3;
      int32 ppid = 4;
      string cwd = 5;
      double cpuPercent = 6;
      float memPercent = 7;
      ProcessIOCountersStat ioCounters = 8;
      string createTime = 9;
      bool isRunning = 10;
      bool isForeground = 11;
      repeated string cmdline = 12;
      // 🦜 : the following 2 need to be obtained from the server
      string exePath = 13;          // 🦜 : path of the exe file
      string hash = 14;             // 🦜 : hash of the exe file
      }
     */
    @Data
    @Entity
    public static class ProcessInfo2 {
        public String name;
        public String user;
        @Id
        public Integer pid;
        public Integer ppid;
        public String cwd;
        public Double cpuPercent;
        public Float memPercent;
        public Long readCount;
        public Long writeCount;
        public Long readBytes;
        public Long writeBytes;
        public String createTime;
        public Boolean isRunning;
        public Boolean isForeground;

        // @CollectionTable(name = "CMD")
        @OrderColumn // Enables persistent order, Defaults to IMAGES_ORDER
        @Column(name = "CMDCMD")
        @ElementCollection(fetch = FetchType.EAGER)          // 🦜: make it a separate table (kinda have to do it when list becomes too big)
        public List<String> cmdline = new ArrayList<>();

        // public String exeName;            // 🦜 : I feels like saving this in db would be easier for us to query
        public String exePath;
        public String hash;


        public static ProcessInfo2 convertFromPb(ProcessInfo i){
            ProcessInfo2 o = new ProcessInfo2();
            o.setName(i.getName());
            o.setUser(i.getUser());
            o.setPid(i.getPid());
            o.setPpid(i.getPpid());
            o.setCwd(i.getCwd());
            o.setCpuPercent(i.getCpuPercent());
            o.setMemPercent(i.getMemPercent());
            o.setReadCount(i.getIoCounters().getReadCount());
            o.setWriteCount(i.getIoCounters().getWriteCount());
            o.setReadBytes(i.getIoCounters().getReadBytes());
            o.setWriteBytes(i.getIoCounters().getWriteBytes());
            o.setCreateTime(i.getCreateTime());
            o.setIsRunning(i.getIsRunning());
            o.setIsForeground(i.getIsForeground());

            // // o.setCmd(i.getCmdList());
            // /*
            //   🦜 : There's something weird about persisting the List, so Let's
            //   just convert from google's List to java's List...
            //  */
            for (String s : i.getCmdlineList()) {
                // cap the length to 254
                if (s.length() > 254){
                    s = s.substring(0, 254);
                }
                o.cmdline.add(s);
            }

            /*
              cap the Cmd to 5, also cap each entry to 100

              🦜 : Shouldn't be a big issure, right?

              🐢 : It seems like if these numbers are *too big* or *too small*, hibernate will
              fail to save it to db......Kinda mysterious...

              🦜 : Nope, it turns out that failing to save is because pb's List
              is mutable which is not acceptable by Hibernate. The solution is
              to make sure that the List is an immutable one.....

             */
            // if exePath is "unknown"
            o.setExePath(i.getExePath());
            o.setHash(i.getHash());
            return o;
        }

        abstract public static class TeXBar implements ITeXWorkable {
            public Double xe;
            public String text;
            public Integer n;
            public String color;
            public static Double y = 1.0;
            public static final List<Integer> colorGradients = Arrays.asList(90,80,70,60,50,40,30,20,10);

            public String getTeXEmpty(){return "";}
            public TeXBar(Double xe, String text, Integer n,
                            String color
                          ){
                this.xe = xe;
                this.text = text;
                this.n = n;
                this.color = color;
            }

            public String toTeXLine(){
                // \aSimpleBar{\x}{-2\y}{7cm}{\texttt{node[pid=1234] 45\%}}
                String xee = String.format("%.2fcm", 0.2 + xe);
                String yy = String.format("%.2fcm", - n * y);
                String r = "\\aSimpleBar[" + color + "]{0.2cm}{" + yy +  "}{" + xee + "}{" + text + "}";
                return r.replaceAll("_", "\\\\_");
            }
            public TeXBar(){}
        }

        public static class CpuTeXBar extends TeXBar {
            public CpuTeXBar(){}
            public CpuTeXBar(Double xe, String text, Integer n, String c){
                super(xe, text, n,c);
            }
            public String getTeXTemplate (){
                return "cpu-bar.tex";
            }

            public static List<CpuTeXBar> make(List<ProcessInfo2> l){
                /*
                  🦜 : convert it. In particular, we need to calculate the `xe`, which should be

                    xe = X * o.cpuPercent/ max(cpuPercent)
                    // where X is pre-set such as 10cm

                    the maximum of cpuPercent is that of the first one.
                 */
                Double X = 10.0;
                List<CpuTeXBar> l2 = new ArrayList<CpuTeXBar>();
                for (int i = 0; i < l.size(); i++){
                    ProcessInfo2 o = l.get(i);
                    Double xe = X * o.cpuPercent / l.get(0).cpuPercent;
                    String text = "\\texttt{" + o.name + "[pid=" + o.pid + "] " + String.format("%.2f", o.cpuPercent) + "\\%}";
                    // \aSimpleBar[\mycola!40]{\x}{-\y}{5cm}{\texttt{chrome[pid=1234] 23\%}}
                    String col = "\\mycola!" + colorGradients.get(i % colorGradients.size());
                    l2.add(new CpuTeXBar(xe, text, i + 1, col));
                }

                return l2;
            }
        }

        public static class MemoryTeXBar extends TeXBar {
            public MemoryTeXBar(){}
            public MemoryTeXBar(Double xe, String text, Integer n, String c){
                super(xe, text, n,c);
            }
            public String getTeXTemplate (){
                return "memory-bar.tex";
            }

            public static List<MemoryTeXBar> make(List<ProcessInfo2> l){
                /*
                  🦜 : convert it. In particular, we need to calculate the `xe`, which should be

                    xe = X * o.cpuPercent/ max(cpuPercent)
                    // where X is pre-set such as 10cm

                    the maximum of cpuPercent is that of the first one.
                 */
                Double X = 10.0;
                List<MemoryTeXBar> l2 = new ArrayList<MemoryTeXBar>();
                for (int i = 0; i < l.size(); i++){
                    ProcessInfo2 o = l.get(i);
                    Double xe = X * o.memPercent / l.get(0).memPercent;
                    String text = "\\texttt{" + o.name + "[pid=" + o.pid + "] " + String.format("%.2f", o.memPercent) + "\\%}";
                    String col = "\\mycola!" + colorGradients.get(i % colorGradients.size());
                    l2.add(new MemoryTeXBar(xe, text, i + 1, col));
                }
                return l2;
            }
        }

        public static class ReadTeXBar extends TeXBar {
            public ReadTeXBar(){}
            public ReadTeXBar(Double xe, String text, Integer n, String c){
                super(xe, text, n,c);
            }
            public String getTeXTemplate (){
                return "disk-read-bar.tex";
            }

            public static List<ReadTeXBar> make(List<ProcessInfo2> l){
                /*
                  🦜 : convert it. In particular, we need to calculate the `xe`, which should be

                  xe = X * o.cpuPercent/ max(cpuPercent)
                  // where X is pre-set such as 10cm

                  the maximum of cpuPercent is that of the first one.
                */
                Double X = 10.0;
                List<ReadTeXBar> l2 = new ArrayList<ReadTeXBar>();
                for (int i = 0; i < l.size(); i++){
                    ProcessInfo2 o = l.get(i);
                    Double xe = X * o.readBytes / l.get(0).readBytes;
                    String text = "\\texttt{" + o.name + "[pid=" + o.pid + "] " +
                        TeXKvEntry.humanizeBytes(o.readBytes) + "}";
                    String col = "\\mycola!" + colorGradients.get(i % colorGradients.size());
                    l2.add(new ReadTeXBar(xe, text, i + 1, col));
                }
                return l2;
            }
        }

        public static class WriteTeXBar extends TeXBar {
            public WriteTeXBar(){}
            public WriteTeXBar(Double xe, String text, Integer n, String c){
                super(xe, text, n,c);
            }
            public String getTeXTemplate (){
                return "disk-write-bar.tex";
            }

            public static List<WriteTeXBar> make(List<ProcessInfo2> l){
                /*
                  🦜 : convert it. In particular, we need to calculate the `xe`, which should be

                  xe = X * o.cpuPercent/ max(cpuPercent)
                  // where X is pre-set such as 10cm

                  the maximum of cpuPercent is that of the first one.
                */
                Double X = 10.0;
                List<WriteTeXBar> l2 = new ArrayList<WriteTeXBar>();
                for (int i = 0; i < l.size(); i++){
                    ProcessInfo2 o = l.get(i);
                    Double xe = X * o.writeBytes / l.get(0).writeBytes;
                    String text = "\\texttt{" + o.name + "[pid=" + o.pid + "] " +
                        TeXKvEntry.humanizeBytes(o.writeBytes) + "}";
                    String col = "\\mycola!" + colorGradients.get(i % colorGradients.size());
                    l2.add(new WriteTeXBar(xe, text, i + 1, col));
                }
                return l2;
            }
        }


    }

    @StaticMetamodel(ProcessInfo2.class)
    public static abstract class ProcessInfo2_ {
        public static volatile SingularAttribute<ProcessInfo2, String> name;
        public static volatile SingularAttribute<ProcessInfo2, String> user;
        // public static volatile SingularAttribute<ProcessInfo2, Integer> pid;
        // public static volatile SingularAttribute<ProcessInfo2, Integer> ppid;
        // public static volatile SingularAttribute<ProcessInfo2, String> cwd;
        public static volatile SingularAttribute<ProcessInfo2, Double> cpuPercent;
        public static volatile SingularAttribute<ProcessInfo2, Float> memPercent;
        public static volatile SingularAttribute<ProcessInfo2, Long> readCount;
        public static volatile SingularAttribute<ProcessInfo2, Long> writeCount;
        public static volatile SingularAttribute<ProcessInfo2, Long> readBytes;
        public static volatile SingularAttribute<ProcessInfo2, Long> writeBytes;
        public static volatile SingularAttribute<ProcessInfo2, String> createTime;
        public static volatile SingularAttribute<ProcessInfo2, Boolean> isRunning;
        public static volatile SingularAttribute<ProcessInfo2, Boolean> isForeground;
        // public static volatile SingularAttribute<ProcessInfo2, String> exePath;
        public static volatile SingularAttribute<ProcessInfo2, String> hash;

        public static Specification<ProcessInfo2> nameEquals(String name) {
            return (root, query, builder) -> builder.equal(root.get(ProcessInfo2_.name), name);
        }
        public static Specification<ProcessInfo2> userEquals(String user) {
            return (root, query, builder) -> builder.equal(root.get(ProcessInfo2_.user), user);
        }
        public static Specification<ProcessInfo2> hashEquals(String hash) {
            return (root, query, builder) -> builder.equal(root.get(ProcessInfo2_.hash), hash);
        }
    }


    @Data
    @MappedSuperclass
    public static class ProcessRegisterBase{
        String userName;        // empty means "any"
        String exeName;
        String hash;            // empty means skipping hash check
        String type;            // can be "ok" | "must"
        String reason;
    }

    @StaticMetamodel(ProcessRegisterBase.class)
    public static abstract class ProcessRegisterBase_ {
        public static volatile SingularAttribute<ProcessRegisterBase, String> userName;
        public static volatile SingularAttribute<ProcessRegisterBase, String> exeName;
        public static volatile SingularAttribute<ProcessRegisterBase, String> hash;
        public static volatile SingularAttribute<ProcessRegisterBase, String> type;

        // --------------------------------------------------
        // 🦜 : for ProcessRegister
        public static Specification<ProcessRegister> userNameEquals(String userName) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.userName), userName);
        }
        public static Specification<ProcessRegister> userNameContains(String userName) {
            return (root, query, builder) -> builder.like(root.get(ProcessRegisterBase_.userName), "%" + userName + "%");
        }

        public static Specification<ProcessRegister> exeNameEquals(String exeName) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.exeName), exeName);
        }
        public static Specification<ProcessRegister> exeNameContains(String exeName) {
            return (root, query, builder) -> builder.like(root.get(ProcessRegisterBase_.exeName), "%" + exeName + "%");
        }
        public static Specification<ProcessRegister> hashEquals(String hash) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.hash), hash);
        }
        public static Specification<ProcessRegister> typeEquals(String type) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.type), type);
        }

        // --------------------------------------------------
        // 🦜 : for ProcessRegisterFailure

        public static Specification<ProcessRegisterFailure> userNameContains_f(String userName) {
            return (root, query, builder) -> builder.like(root.get(ProcessRegisterBase_.userName), "%" + userName + "%");
        }

        public static Specification<ProcessRegisterFailure> exeNameEquals_f(String exeName) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.exeName), exeName);
        }

        public static Specification<ProcessRegisterFailure> exeNameContains_f(String exeName) {
            return (root, query, builder) -> builder.like(root.get(ProcessRegisterBase_.exeName), "%" + exeName + "%");
        }
        public static Specification<ProcessRegisterFailure> hashEquals_f(String hash) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.hash), hash);
        }
        public static Specification<ProcessRegisterFailure> typeEquals_f(String type) {
            return (root, query, builder) -> builder.equal(root.get(ProcessRegisterBase_.type), type);
        }
    }

    public static class TeXKvEntry implements ITeXWorkable {
        String key;
        String value;

        public TeXKvEntry(String key, String value){
            this.key = key;
            this.value = value;
        }

        public static String humanizeBytes(Long bytes){
            if (bytes < 1024){
                return bytes + "B";
            }else if (bytes < 1024 * 1024){
                return String.format("%.2f", bytes / 1024.0) + "KB";
            }else if (bytes < 1024 * 1024 * 1024){
                return String.format("%.2f", bytes / 1024.0 / 1024.0) + "MB";
            }else {
                return String.format("%.2f", bytes / 1024.0 / 1024.0 / 1024.0) + "GB";
            }
        }

        @JsonIgnore
        public String getTeXEmpty(){
            return "";
        }
        @JsonIgnore
        public String getTeXTemplate (){
            return "kv.tex";
        }
        public String toTeXLine(){
            return "\\MySet{" + key + "}{" + value + "}";
        }
    }

    @Data
    @Entity
    @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
    public static class ProcessRegister extends ProcessRegisterBase
                                                implements ITeXWorkable {
        /*
          register /rĕj′ĭ-stər/
          noun
          1. A formal or official recording of items, names, or actions.
          2. A book for such entries.
          3. An entry in such a record. <- 🦜 : here we mean this
         */
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        public Date createTime;

        /*
          🦜 : ⚠️ When you define a ctor, the default ctor is gone....
         */
        public static  ProcessRegister MakeOkRegister(String exeName,  String reason) {
            ProcessRegister o = new ProcessRegister();
            // o.userName = "";
            o.exeName = exeName;
            // o.hash = "";
            o.type = "ok";
            o.reason = reason;
            o.createTime = new Date();
            return o;
        }

        @JsonIgnore
        public String getTeXEmpty(){
            return "-- & -- & -- & -- & -- \\\\";
        }
        @JsonIgnore
        public String getTeXTemplate (){
            return "process-register.tex";
        }
        public static String texttt(String s){
            if (s == null || s.length() == 0){
                return "--";
            }
            return "\\texttt{" + s + "}";
        }
        public String toTeXLine(){

            /*
              🦜 : For each, write the line
              <user> & <name> & <hash> & <type> & <time> \\
            */
            String s = "";
            s += texttt(userName) + " & ";
            s += texttt(exeName) + " & ";
            s += texttt(hash) + " & ";
            s += (type == "ok" ? "注册允许应用" : "关键应用") + " & ";
            // YYYY - MM - DD HH: MM: SS
            s += old2New(createTime).format(dtf) + " \\\\";

            // replace all the `_` with `\_`
            return s.replaceAll("_", "\\\\_");
        }

    }

    @StaticMetamodel(ProcessRegister.class)
    public static abstract class ProcessRegister_ extends ProcessRegisterBase_ {
        public static volatile SingularAttribute<ProcessRegister, Long> id;
    }

    @Data
    @Entity
    @EqualsAndHashCode(callSuper = false) // if base class is @Data, ignore warning from lombok
    public static class ProcessRegisterFailure extends ProcessRegisterBase
                                                       implements ITeXWorkable {
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        public Long id;
        Date timeOfDiscovery;

        @JsonIgnore
        public String getTeXEmpty(){
            return "-- & -- & -- & -- & -- \\\\";
        }
        @JsonIgnore
        public String getTeXTemplate (){
            return "process-failure.tex";
        }
        public String toTeXLine(){

            /*
              🦜 : For each, write the line
              <user> & <name> & <hash> & <reason> & <time> \\
            */
            String s = "";
            s += userName + " & ";
            s += exeName + " & ";
            s += hash + " & ";
            s += reason + " &";
            // YYYY - MM - DD HH: MM: SS
            s += old2New(timeOfDiscovery).format(dtf) + " \\\\";

            // replace all the `_` with `\_`
            return s.replaceAll("_", "\\\\_");
        }
    }

    @StaticMetamodel(ProcessRegisterFailure.class)
    public static abstract class ProcessRegisterFailure_ extends ProcessRegisterBase_ {
        public static volatile SingularAttribute<ProcessRegisterFailure, Long> id;
        public static volatile SingularAttribute<ProcessRegisterFailure, Date> timeOfDiscovery;
    }

}
